package com.pandi.springbootinfluxdb.v02;


import lombok.extern.slf4j.Slf4j;
import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
import org.influxdb.InfluxDBMapperException;
import org.influxdb.annotation.Column;
import org.influxdb.annotation.Measurement;
import org.influxdb.dto.BatchPoints;
import org.influxdb.dto.Point;
import org.influxdb.dto.Query;
import org.influxdb.dto.QueryResult;

import java.lang.reflect.Field;
import java.time.Instant;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * 继承InfluxDBMapper，后续可扩展
 */
@Slf4j
public class InfluxMapper extends InfluxDBResultMapper {

    private InfluxDB influxDB;

    public InfluxMapper(String url, String userName, String password, String database,
                        String retentionPolicy) {
        this.influxDB = influxDbBuild(url, userName, password, database, retentionPolicy);
    }

    /**
     * 连接数据库 ，若不存在则创建
     *
     * @return influxDb
     */
    private InfluxDB influxDbBuild(String url, String userName, String password, String database,
                                   String retentionPolicy) {
        if (this.influxDB != null) {
            return this.influxDB;
        }

        this.influxDB = InfluxDBFactory.connect(url, userName, password);

        if (!this.influxDB.databaseExists(database)) {
            try {
                String createDatabaseQueryString = String.format("CREATE DATABASE \"%s\"", database);
                this.influxDB.query(new Query(createDatabaseQueryString));
            } catch (Exception e) {
                log.error("Create influx db failed, cause by: {}", e.getMessage());
            } finally {
                this.influxDB.setRetentionPolicy(retentionPolicy);
            }
        }

        this.influxDB.setDatabase(database);
        this.influxDB.setLogLevel(InfluxDB.LogLevel.BASIC);
        return this.influxDB;
    }


    public <T> List<T> query(Query query, Class<T> clazz) {
        this.throwExceptionIfMissingAnnotation(clazz);
        QueryResult queryResult = this.influxDB.query(query);
        return this.toPOJO(queryResult, clazz);
    }

    public <T> List<T> query(Class<T> clazz) {
        this.throwExceptionIfMissingAnnotation(clazz);
        String measurement = this.getMeasurementName(clazz);
        String database = this.getDatabaseName(clazz);
        if ("[unassigned]".equals(database)) {
            throw new IllegalArgumentException(Measurement.class.getSimpleName() + " of class " + clazz.getName() + " should specify a database value for this operation");
        } else {
            QueryResult queryResult = this.influxDB.query(new Query("SELECT * FROM " + measurement, database));
            return this.toPOJO(queryResult, clazz);
        }
    }

//    public <T> List<T> queryLast(QueryLastCondition queryLastCondition, Class<T> clazz) {
//        String table = queryLastCondition.getTable();
//        Map<String, String> condition = queryLastCondition.getTags();
//
//        if (StringUtils.isEmpty(table)) {
//            return new ArrayList<>();
//        }
//
//        StringBuilder sb = new StringBuilder();
//        if (null != condition && condition.keySet().size() > 0) {
//            sb.append(" WHERE ");
//            for (Map.Entry<String, String> entry : condition.entrySet()) {
//                String tagKey = entry.getKey();
//                String tagValue = entry.getValue();
//                sb.append(String.format("%s = '%s' AND ", tagKey, tagValue));
//            }
//            sb.replace(sb.length() - 4, sb.length(), "");
//        } else {
//            sb.replace(0, sb.length(), "");
//        }
//
//        String sql = MessageFormat.format("SELECT * FROM {0} {1} GROUP BY * ORDER BY DESC LIMIT 1", table, sb.toString());
//        Query query = new Query(sql);
//        QueryResult queryResult = this.influxDB.query(query);
//
//        return this.toPOJO(queryResult, clazz);
//    }
//
//    public <T> List<T> queryTimeRange(QueryTimeRangeCondition queryTimeRangeCondition, Class<T> clazz) {
//        String table = queryTimeRangeCondition.getTable();
//        Map<String, String> tags = queryTimeRangeCondition.getTags();
//        Long startTime = queryTimeRangeCondition.getStartTime();
//        Long endTime = queryTimeRangeCondition.getEndTime();
//        String period = queryTimeRangeCondition.getPeriod();
//
//        if (StringUtils.isEmpty(table)) {
//            return new ArrayList<>();
//        }
//
//        StringBuilder sb = new StringBuilder();
//
//        if (null != tags && tags.keySet().size() > 0) {
//            sb.append(" WHERE ");
//            for (Map.Entry<String, String> entry : tags.entrySet()) {
//                String tagKey = entry.getKey();
//                String tagValue = entry.getValue();
//                sb.append(String.format("%s = '%s' AND ", tagKey, tagValue));
//            }
//            sb.replace(sb.length() - 4, sb.length(), "");
//        } else {
//            sb.replace(0, sb.length(), "");
//        }
//
//        if(period == null || period.length() < 2){
//            if (startTime != null && endTime != null) {
//                if (String.valueOf(startTime).length() == 10 && String.valueOf(endTime).length() == 10) {
//                    //转化维纳秒
//                    startTime = startTime * 1000 * 1000000;
//                    endTime = endTime * 1000 * 1000000;
//                } else if (String.valueOf(startTime).length() == 13 && String.valueOf(endTime).length() == 13) {
//                    //转化维纳秒
//                    startTime = startTime * 1000000;
//                    endTime = endTime * 1000000;
//                } else if (String.valueOf(endTime).length() == 19) {
//                    //转化维纳秒
//                } else {
//                    return new ArrayList<>();
//                }
//
//                sb.append(String.format(" AND time >= %s AND time < %s ", startTime, endTime));
//            } else if (startTime != null) {
//                if (String.valueOf(startTime).length() == 10) {
//                    //转化维纳秒
//                    startTime = startTime * 1000 * 1000000;
//                } else if (String.valueOf(startTime).length() == 13) {
//                    //转化维纳秒
//                    startTime = startTime * 1000000;
//                } else if (String.valueOf(startTime).length() == 19) {
//                    //转化维纳秒
//                } else {
//                    return new ArrayList<>();
//                }
//                sb.append(String.format(" AND time >= %s ", startTime));
//            } else if (endTime != null) {
//                if (String.valueOf(endTime).length() == 10) {
//                    //转化维纳秒
//                    endTime = endTime * 1000 * 1000000;
//                } else if (String.valueOf(endTime).length() == 13) {
//                    //转化维纳秒
//                    endTime = endTime * 1000000;
//                } else if (String.valueOf(endTime).length() == 19) {
//
//                    //转化维纳秒
//                } else {
//                    return new ArrayList<>();
//                }
//                sb.append(String.format(" AND time < %s ", endTime));
//            }
//        }else{
//            QueryLastCondition queryLastCondition = new QueryLastCondition();
//            queryLastCondition.setTable(table);
//            queryLastCondition.setTags(tags);
//
//            List<T> lastResult = this.queryLast(queryLastCondition, clazz);
//
//            if(lastResult == null || lastResult.size() == 0){
//                return new ArrayList<>();
//            }
//
//            T model = lastResult.get(0);
//            ConcurrentMap colNameAndFieldMap = this.getColNameAndFieldMap(model.getClass());
//            try {
//                Class<?> modelType = model.getClass();
//                Iterator var11 = colNameAndFieldMap.keySet().iterator();
//
//                while (var11.hasNext()) {
//                    String key = (String) var11.next();
//                    Field field = (Field) colNameAndFieldMap.get(key);
//                    Column column = (Column) field.getAnnotation(Column.class);
//                    String columnName = column.name();
//                    Class<?> fieldType = field.getType();
//                    if (!field.isAccessible()) {
//                        field.setAccessible(true);
//                    }
//
//                    Object value = field.get(model);
//                    if ("time".equals(columnName)) {
//                        if (value != null) {
//                            endTime = ((Instant) value).toEpochMilli() * 1000000;
//                        }
//                    }
//                }
//            } catch (IllegalAccessException var18) {
//                throw new InfluxDBMapperException(var18);
//            }
//
//            if(period.contains("h")){
//                String numStr = period.substring(0, period.length() - 1);
//                int num = Integer.parseInt(numStr);
//                startTime = endTime - num * 60 * 60 * 1000 * 1000000;
//            }else if(period.contains("m")){
//                String numStr = period.substring(0, period.length() - 1);
//                int num = Integer.parseInt(numStr);
//                startTime = endTime - num * 60 * 1000 * 1000000;
//            }else if(period.contains("s")){
//                String numStr = period.substring(0, period.length() - 1);
//                int num = Integer.parseInt(numStr);
//                startTime = endTime - num * 1000 * 1000000;
//            }
//
//            sb.append(String.format(" AND time >= %s AND time < %s ", startTime, endTime)+"tz('Asia/Shanghai')");
//        }
//
//        String sql = MessageFormat.format("SELECT * FROM {0} {1}", table, sb.toString());
//        Query query = new Query(sql);
//        QueryResult queryResult = this.influxDB.query(query);
//
//        return this.toPOJO(queryResult, clazz);
//    }
//
//    public List<TagValue> tagValues(String tagKey, String table) {
//        final String sql = MessageFormat.format("SHOW TAG VALUES FROM {0} WITH KEY={1}", table, tagKey);
//        QueryResult queryResult = this.influxDB.query(new Query(sql));
//
//        Map<String, TagValue> tagValueMap = new HashMap<>();
//
//        queryResult.getResults().stream().filter((internalResult) -> {
//            return Objects.nonNull(internalResult) && Objects.nonNull(internalResult.getSeries());
//        }).forEach((internalResult) -> {
//            internalResult.getSeries().stream().forEach((series) -> {
//                List<List<Object>> values = series.getValues();
//                values.forEach(value -> {
//                    String columnKey = (String) value.get(0);
//                    String columnValue = (String) value.get(1);
//
//                    if (tagValueMap.containsKey(columnKey)) {
//                        TagValue tagValue = tagValueMap.get(columnKey);
//                        tagValue.getValues().add(columnValue);
//                    } else {
//                        TagValue tagValue = new TagValue();
//                        tagValue.setKey(columnKey);
//                        tagValue.getValues().add(columnValue);
//                        tagValueMap.put(columnKey, tagValue);
//                    }
//                });
//            });
//        });
//
//        return new ArrayList<>(tagValueMap.values());
//    }

    public <T> void save(T model) {
        this.throwExceptionIfMissingAnnotation(model.getClass());
        this.cacheMeasurementClass(new Class[]{model.getClass()});
        ConcurrentMap colNameAndFieldMap = this.getColNameAndFieldMap(model.getClass());

        try {
            Class<?> modelType = model.getClass();
            String measurement = this.getMeasurementName(modelType);
            String database = this.getDatabaseName(modelType);
            String retentionPolicy = this.getRetentionPolicy(modelType);
            TimeUnit timeUnit = this.getTimeUnit(modelType);
            long time = timeUnit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
            Point.Builder pointBuilder = Point.measurement(measurement).time(time, timeUnit);
            Iterator var11 = colNameAndFieldMap.keySet().iterator();

            while (var11.hasNext()) {
                String key = (String) var11.next();
                Field field = (Field) colNameAndFieldMap.get(key);
                Column column = (Column) field.getAnnotation(Column.class);
                String columnName = column.name();
                Class<?> fieldType = field.getType();
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                Object value = field.get(model);
                if (column.tag()) {
                    pointBuilder.tag(columnName, value.toString());
                } else if ("time".equals(columnName)) {
                    if (value != null) {
                        this.setTime(pointBuilder, fieldType, timeUnit, value);
                    }
                } else {
                    this.setField(pointBuilder, fieldType, columnName, value);
                }
            }

            Point point = pointBuilder.build();
            if ("[unassigned]".equals(database)) {
                this.influxDB.write(point);
            } else {
                this.influxDB.write(database, retentionPolicy, point);
            }

        } catch (IllegalAccessException var18) {
            throw new InfluxDBMapperException(var18);
        }
    }

    public <T> void saveBatch(List<T> models) {
        BatchPoints batchPoints = null;
        for (T model : models) {
            this.throwExceptionIfMissingAnnotation(model.getClass());
            this.cacheMeasurementClass(new Class[]{model.getClass()});
            ConcurrentMap colNameAndFieldMap = this.getColNameAndFieldMap(model.getClass());

            try {
                Class<?> modelType = model.getClass();
                String measurement = this.getMeasurementName(modelType);
                String database = this.getDatabaseName(modelType);
                String retentionPolicy = this.getRetentionPolicy(modelType);
                TimeUnit timeUnit = this.getTimeUnit(modelType);
                long time = timeUnit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);

                if (batchPoints == null) {
                    batchPoints = BatchPoints.database(database).retentionPolicy(retentionPolicy).build();
                }

                Point.Builder pointBuilder = Point.measurement(measurement).time(time, timeUnit);
                Iterator var11 = colNameAndFieldMap.keySet().iterator();

                while (var11.hasNext()) {
                    String key = (String) var11.next();
                    Field field = (Field) colNameAndFieldMap.get(key);
                    Column column = (Column) field.getAnnotation(Column.class);
                    String columnName = column.name();
                    Class<?> fieldType = field.getType();
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }

                    Object value = field.get(model);
                    if (column.tag()) {
                        pointBuilder.tag(columnName, value.toString());
                    } else if ("time".equals(columnName)) {
                        if (value != null) {
                            this.setTime(pointBuilder, fieldType, timeUnit, value);
                        }
                    } else {
                        this.setField(pointBuilder, fieldType, columnName, value);
                    }
                }

                Point point = pointBuilder.build();
                batchPoints.point(point);
            } catch (IllegalAccessException var18) {
                throw new InfluxDBMapperException(var18);
            }
        }
        ;

        if (batchPoints != null) {
            this.influxDB.write(batchPoints);
        }

    }

    private void setTime(Point.Builder pointBuilder, Class<?> fieldType, TimeUnit timeUnit, Object value) {
        if (Instant.class.isAssignableFrom(fieldType)) {
            Instant instant = (Instant) value;
            long time = timeUnit.convert(instant.toEpochMilli(), TimeUnit.MILLISECONDS);
            pointBuilder.time(time, timeUnit);
        } else {
            throw new InfluxDBMapperException("Unsupported type " + fieldType + " for time: should be of Instant type");
        }
    }

    private void setField(Point.Builder pointBuilder, Class<?> fieldType, String columnName, Object value) {
        if (!Boolean.TYPE.isAssignableFrom(fieldType) && !Boolean.class.isAssignableFrom(fieldType)) {
            if (!Long.TYPE.isAssignableFrom(fieldType) && !Long.class.isAssignableFrom(fieldType)) {
                if (!Double.TYPE.isAssignableFrom(fieldType) && !Double.class.isAssignableFrom(fieldType)) {
                    if (!Integer.TYPE.isAssignableFrom(fieldType) && !Integer.class.isAssignableFrom(fieldType)) {
                        if (!String.class.isAssignableFrom(fieldType)) {
                            throw new InfluxDBMapperException("Unsupported type " + fieldType + " for column " + columnName);
                        }

                        pointBuilder.addField(columnName, (String) value);
                    } else {
                        pointBuilder.addField(columnName, (long) (Integer) value);
                    }
                } else {
                    pointBuilder.addField(columnName, (Double) value);
                }
            } else {
                pointBuilder.addField(columnName, (Long) value);
            }
        } else {
            pointBuilder.addField(columnName, (Boolean) value);
        }

    }
}
